﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Collections.Generic;
using System.Windows.Media.DirectShow;

namespace System.Windows.Media.DirectShow.Avi
{
    public class AviFileParser
    {

        private BinaryReader reader;
        private long? dataPosition;
        private AviPacket lastPacket;

        public AviFileParser(BinaryReader reader)
        {
            this.reader = reader;
        }

        public AviFile Parse()
        {
            dataPosition = null;
            lastPacket = null;

            FourCC riff = reader.ReadInt32();
            if (riff != "RIFF")
                throw new ArgumentException("Invalid file format");

            int fileLength = reader.ReadInt32();

            FourCC avi = reader.ReadInt32();
            if (avi != "AVI ")
                throw new ArgumentException("Invalid file format");

            AviFile file = new AviFile();

            bool r = ParseList(file.Lists, false);

            return file;
        }

        private bool ParseList(IList<AviList> aviList, bool skipList)
        {
            if (!skipList)
            {
                FourCC list = reader.ReadInt32();
                if (list != "LIST")
                    throw new FormatException("Expecting LIST");

                aviList.Add(new AviList());
            }

            int listLength = reader.ReadInt32();
            long startList = reader.BaseStream.Position;

            //while (startList + listLength > reader.BaseStream.Position)
            while (true)
            {
                string listType = ((FourCC)reader.ReadInt32()).Value;
                switch (listType)
                {
                    case "hdrl":
                        AviHeader aviHeader = ParseHdrl();
                        aviList[aviList.Count - 1].Chunks.Add(new AviChunk
                        {
                            Name = listType,
                            Value = aviHeader,
                        });
                        break;
                    case "odml":
                        aviList[aviList.Count - 1].Name = listType;
                        break;
                    case "dmlh":
                        int size = reader.ReadInt32();
                        int totalFrames = reader.ReadInt32();
                        SkipBytes(size - 4);
                        break;
                    case "strl":
                        break;
                    case "LIST":
                        var l = aviList[aviList.Count - 1];
                        l.Lists.Add(new AviList());
                        if (ParseList(l.Lists, true))
                            return true;
                        break;
                    case "ncdt":
                        // unsupported yet
                        FourCC ncvr = reader.ReadInt32();
                        SkipList();
                        break;
                    case "nctg":
                    case "ncth":
                    case "ncvw":
                    case "ncdb":
                        SkipList();
                        break;
                    case "strh":
                        aviList[aviList.Count - 1].Name = listType;

                        AviStreamHeader aviStreamHeader = reader.FillType<AviStreamHeader>();
                        aviList[aviList.Count - 1].Chunks.Add(new AviChunk
                        {
                            Name = listType,
                            Value = aviStreamHeader,
                        });
                        break;
                    case "strf":
                        object h = ParseStreamFormat();
                        aviList[aviList.Count - 1].Chunks.Add(new AviChunk
                        {
                            Name = listType,
                            Value = h,
                        });
                        break;
                    case "JUNK":
                        ProcessJunk();
                        return false;
                    case "movi":
                        aviList[aviList.Count - 1].Name = listType;
                        dataPosition = this.reader.BaseStream.Position;
                        break;
                    case "strd":
                        // unsupported yet
                        SkipList();
                        break;
                    case "indx":
                        // unsupported yet
                        SkipList();
                        break;
                    case "INFO":
                        // unsupported yet
                        FourCC isft = reader.ReadInt32();
                        SkipList();
                        break;
                    default:
                        // TODO: partial audio/video index
                        if (listType.StartsWith("ix"))
                        {
                            SkipBytes(reader.ReadInt32());
                        }
                        else if (listType.EndsWith("wb") || listType.EndsWith("db") || listType.EndsWith("dc"))
                        {
                            // Found data chunks
                            dataPosition = this.reader.BaseStream.Position - 4;
                            return true;
                        }
                        else if (dataPosition.HasValue) // movi found, can exit
                            return true;
                        else
                            throw new FormatException("Unknown " + listType);
                        break;
                }
            }

            return false;
        }

        public AviPacket ReadNextPacket()
        {
            if (!dataPosition.HasValue)
                throw new InvalidOperationException("Cannot read packets. You must parse file before");

            if (lastPacket == null)
                reader.BaseStream.Seek(dataPosition.Value, SeekOrigin.Begin);
            else
                reader.BaseStream.Seek(lastPacket.EndOffset, SeekOrigin.Begin);

            while (true)
            {
                string listType = ((FourCC)reader.ReadInt32()).Value;
                // TODO: Final index
                if (listType == "idx1") return null;

                string moviType = listType.Substring(2);
                int streamIndex = Convert.ToInt32(listType.Substring(0, 2));
                switch (moviType)
                {
                    case "wb":
                        return ProcessStreamData(streamIndex, false);
                    case "db":
                        // Uncompressed
                        return ProcessStreamData(streamIndex, false);
                    case "dc":
                        // Compressed
                        return ProcessStreamData(streamIndex, true);
                    default:
                        throw new FormatException("Unknown packet header" + listType);
                }
            }
        }

        private void SkipList()
        {
            int size = reader.ReadInt32();
            SkipBytes(size);
        }

        private AviPacket ProcessStreamData(int streamIndex, bool compressed)
        {
            long startOffset = reader.BaseStream.Position;
            long endOffset = startOffset + reader.ReadInt32() + 4;
            if (endOffset % 2 != 0)
                endOffset ++;

            lastPacket = new AviPacket(reader.BaseStream, startOffset, endOffset, streamIndex);
            return lastPacket;
        }

        private void ProcessJunk()
        {
            int skip = reader.ReadInt32();
            SkipBytes(skip);
        }

        private void SkipBytes(long skip)
        {
            reader.BaseStream.Seek(skip, SeekOrigin.Current);
        }

        private object ParseStreamFormat()
        {
            int size = reader.ReadInt32();
            if (size == 16)
            {
                return reader.FillType<WaveFormatExtensible>(size);
            }
            else if (size == 18)
            {
                WaveFormatExtensible f = reader.FillType<WaveFormatExtensible>(size);
                return f;
            }
            else if (size == 30) // WAVE_FORMAT_ADPCM
            {
                WaveFormatExtensible f = reader.FillType<WaveFormatExtensible>(size);
                SkipBytes(12);
                // TODO: handle other remaining 12 bytes
                return f;
            }
            else if (size == 40)
                return reader.FillType<BitmapInfoHeader>();
            else
                throw new NotSupportedException("Unknown stream format size");
        }


        private AviHeader ParseHdrl()
        {
            FourCC type = reader.ReadInt32();
            if (type != "avih")
                throw new FormatException("Expecting avih");

            AviHeader ah = reader.FillType<AviHeader>();

            return ah;
        }
    }
}
